#region License

/*
 * Copyright ?2002-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#endregion

#region Imports

using System;
using System.Diagnostics;
using System.Reflection;

#endregion

namespace Spring.Core
{
    /// <summary>
    /// Factory class to conceal any default <see cref="Spring.Core.IControlFlow"/> implementation.
    /// </summary>
    /// <author>Rod Johnson</author>
    /// <author>Simon White (.NET)</author>
    public abstract class ControlFlowFactory
    {
        /// <summary>
        /// Creates a new instance of the <see cref="Spring.Core.IControlFlow"/>
        /// implementation provided by this factory.
        /// </summary>
        /// <returns>
        /// A new instance of the <see cref="Spring.Core.IControlFlow"/>
        /// implementation provided by this factory.
        /// </returns>
        public static IControlFlow CreateControlFlow()
        {
            return new DefaultControlFlow();
        }

        private class DefaultControlFlow : IControlFlow
        {
            private readonly StackTrace _stackTrace;

            #region Constructor (s) / Destructor

            /// <summary>
            /// Creates a new instance of the
            /// <see cref="Spring.Core.ControlFlowFactory.DefaultControlFlow"/> class.
            /// </summary>
            public DefaultControlFlow()
            {
                _stackTrace = new StackTrace();
            }

            #endregion

            #region IControlFlow Members

            /// <summary>
            /// Detects whether the caller is under the supplied <see cref="System.Type"/>,
            /// according to the current stacktrace.
            /// </summary>
            /// <seealso cref="Spring.Core.IControlFlow.Under(Type)"/>
            bool IControlFlow.Under(Type type)
            {
                return IsMatch(new MethodsDeclaredTypeCriteria(type));
            }

            /// <summary>
            /// Detects whether the caller is under the supplied <see cref="System.Type"/>
            /// and <paramref name="methodName"/>, according to the current stacktrace.
            /// </summary>
            /// <remarks>
            /// <p>
            /// Matches the whole method name.
            /// </p>
            /// </remarks>
            /// <seealso cref="Spring.Core.IControlFlow.Under(Type, string)"/>
            bool IControlFlow.Under(Type type, string methodName)
            {
                ComposedCriteria criteria = new();
                criteria.Add(new MethodsDeclaredTypeCriteria(type));
                criteria.Add(new RegularExpressionMethodNameCriteria(methodName));
                return IsMatch(criteria);
            }

            /// <summary>
            /// Does the current stack trace contain the supplied <paramref name="token"/>?
            /// </summary>
            /// <remarks>
            /// <p>
            /// This leaves it up to the caller to decide what matches, but is obviously less of
            /// an abstraction because the caller must know the exact format of the underlying
            /// stack trace.
            /// </p>
            /// </remarks>
            /// <seealso cref="Spring.Core.IControlFlow.UnderToken(string)"/>
            bool IControlFlow.UnderToken(string token)
            {
                return _stackTrace.ToString().IndexOf(token) != -1;
            }

            private bool IsMatch(ICriteria criteria)
            {
                for (int i = 0; i < _stackTrace.FrameCount; i++)
                {
                    MethodBase method = _stackTrace.GetFrame(i).GetMethod();
                    if (criteria.IsSatisfied(method))
                    {
                        return true;
                    }
                }
                return false;
            }

            private sealed class MethodsDeclaredTypeCriteria : ICriteria
            {
                public MethodsDeclaredTypeCriteria(Type typeToMatch)
                {
                    _typeToMatch = typeToMatch;
                }

                public bool IsSatisfied(object datum)
                {
                    MethodBase method = datum as MethodBase;
                    if (method is not null && method.DeclaringType is not null)
                    {
                        return method.DeclaringType.Equals(_typeToMatch);
                    }
                    return false;
                }

                private readonly Type _typeToMatch;
            }

            #endregion
        }
    }
}